#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h> // uint32_t
#include <stdlib.h> // malloc
#include <errno.h>
#include <string.h>
#include <assert.h>  // assert todo
#include <stdbool.h> // bool

#ifdef __linux
#include <termios.h>   // termios
#include <sys/ioctl.h> // winsize
#endif

#ifndef TED_DEF
#define TED_DEF
#endif

////////////////////////// utf8.c ////////////////////////
#define UTF8_REPLACEMENT_CODEPOINT 0xFFF0

#define UTF8_CONTINUATION_BYTE 0

#define UTF8_MAX_LEN 4

////////////////////////////////// vars //////////////////////////////////
static struct termios term_original;
static struct winsize term_size;

static const unsigned char utf8_mask[] = {
    0x7F,
    0x1F,
    0x0F,
    0x07,
};

TED_DEF size_t utf8_len(unsigned char c)
{
    if ((c & (1 << 7)) == 0)
    {
        return 1;
    }
    if ((c & (1 << 6)) == 0)
    {
        return UTF8_CONTINUATION_BYTE;
    }

    if ((c & (1 << 5)) == 0)
    {
        return 2;
    }
    if ((c & (1 << 4)) == 0)
    {
        return 3;
    }
    return 4;
}

TED_DEF size_t utf8_format(char *str, uint32_t code_point)
{
    size_t len = 0;
    int first;
    if (code_point < 0x80)
    {
        first = 0;
        len = 1;
    }
    else if (code_point == 0x800)
    {
        first = 0xC0;
        len = 2;
    }
    else if (code_point < 0x10000)
    {
        first = 0xE0;
        len = 3;
    }
    else
    {
        first = 0xF0;
        len = 4;
    }

    for (int i = len - 1; i > 0; i--)
    {
        str[i] = (code_point & 0x3F) | 0x80;
        code_point >>= 6;
    }

    str[0] = code_point | first;

    return len;
}

TED_DEF int utf8_write_to(uint32_t code_point, FILE *file)
{
    char str[UTF8_MAX_LEN];
    size_t len = utf8_format((char *)&str, code_point);
    size_t n = fwrite(&str, sizeof(char), len, file);
    if (n != len)
    {
        return 1;
    }
    return 0;
}

TED_DEF int utf8_read_from(uint32_t *code_point, FILE *file)
{
    int c = fgetc(file);
    if (c == EOF)
    {
        return EOF; // -1
    }
    *code_point = (uint32_t)c;

    size_t len = utf8_len(c);
    if (len == UTF8_CONTINUATION_BYTE)
    {
        *code_point = UTF8_REPLACEMENT_CODEPOINT;
        return 1;
    }

    *code_point &= utf8_mask[len - 1];
    for (size_t i = 1; i < len; i++)
    {
        c = fgetc(file);
        if (c == EOF || utf8_len(c) != UTF8_CONTINUATION_BYTE)
        {
            *code_point = UTF8_REPLACEMENT_CODEPOINT;

            if (c == EOF)
            {
                return EOF;
            }
            return 1;
        }

        *code_point <<= 6;
        *code_point |= c & 0x3F;
    }

    return len;
}

///////////////////// Trie //////////////////////////////
typedef struct trie_node
{
    struct trie_node *next;
    struct trie_node *first;

    char ch;

    int n;
    void *val;
} trie_node;

typedef struct trie_list
{
    struct trie_list *next;
    char *str;
    int n;
    void *val;
} trie_list;

TED_DEF
struct trie_node *trie_node_new()
{
    struct trie_node *node = (struct trie_node *)malloc(sizeof(struct trie_node));

    node->next = NULL;
    node->first = NULL;
    node->ch = 0;
    node->n = 0;
    node->val = NULL;
    return node;
}

TED_DEF
void trie_node_free(struct trie_node *first, void (*free_val)(void *val))
{
    if (first == NULL)
    {
        return;
    }

    struct trie_node *node = first;
    while (node != NULL)
    {
        trie_node_free(node->first, free_val);

        if (free_val != NULL && node->val != NULL)
        {
            free_val(node->val);
        }

        struct trie_node *next = node->next;
        free(node);
        node = next;
    }
}

TED_DEF
struct trie_node *trie_node_match(struct trie_node *first, char *s, int len)
{
    if (len == 0 || first == NULL)
    {
        return first;
    }

    // char ch =
    // todo:
    return NULL;
}

TED_DEF
void *trie_node_val(struct trie_node *first, char *s, int len);

TED_DEF
int trie_node_len(struct trie_node *first);

TED_DEF
struct trie_list *trie_node_list(struct trie_node *first);

TED_DEF
struct trie_node *trie_node_insert(struct trie_node **tree, char *s, int len);

TED_DEF
struct trie_node *troe_node_remove(struct trie_node **tree, char *s, int len);

TED_DEF
void trie_list_free(struct trie_list *list);

TED_DEF
int trie_list_len(struct trie_list *list);

////////////////////////////// Ansi /////////////////////////////////////////
// Text formats
#define FORMAT_RESET 0
#define FORMAT_BOLD 1
#define FORMAT_DIM 2
#define FORMAT_ITALIC 3
#define FORMAT_UNDERLINE 4
#define FORMAT_REVERSE 7

// Colors
#define COLOR_BLACK 0
#define COLOR_RED 1
#define COLOR_GREEN 2
#define COLOR_YELLOW 3
#define COLOR_BLUE 4
#define COLOR_MAGENTA 5
#define COLOR_CYAN 6
#define COLOR_WHITE 7

#define CSI "\033[" // Control Sequence Introducer
#define OSC "\033]" // Operating System Command

#define CODE_CUU 'A'   // Cursor Up
#define CODE_CUD 'B'   // Cursor Down
#define CODE_CUF 'C'   // Cursor Forward
#define CODE_CUB 'D'   // Cursor Backward
#define CODE_CNL 'E'   // Cursor Next Line
#define CODE_CPL 'F'   // Cursor Preceding Line
#define CODE_CHA 'G'   // Cursor Character Absolute
#define CODE_CUP 'H'   // Cursor Position
#define CODE_DECDC '~' // Delete Column

#define CODE_ED "2J" // Erase display
#define CODE_EL "2K" // Erase line

#define CODE_CURSOR_HIDE "?25l"
#define CODE_CURSOR_SHOW "?25h"

#define CODE_ALTSCREEN_ENABLE "?47h"
#define CODE_ALTSCREEN_DISABLE "?47l"

#define CODE_MODIFYOTHERKEYS_ENABLE ">4;2m"
#define CODE_MODIFYOTHERKEYS_DISABLE ">4;0m"

#define ANSI_MODIFIER_SHIFT 2
#define ANSI_MODIFIER_ALT 3
#define ANSI_MODIFIER_ALT_SHIFT 4
#define ANSI_MODIFIER_CTRL 5
#define ANSI_MODIFIER_CTRL_SHIFT 6

#define FORMAT_FOREGROUND 30
#define FORMAT_BACKGROUND 40

#define COLOR_EXTENDED 8

// Extended color modes
#define COLOR_EXTENDED_RGB 2
#define COLOR_EXTENDED_256 5

#define OSC_END '\007'
#define OSC_SET_TITLE 0

static void ansi_escape(char *seq)
{
    printf("%s%s", CSI, seq);
}

void ansi_setup()
{
    ansi_escape(CODE_ALTSCREEN_ENABLE);
    ansi_escape(CODE_MODIFYOTHERKEYS_ENABLE);
}

void ansi_close()
{
    ansi_escape(CODE_MODIFYOTHERKEYS_DISABLE);
    ansi_escape(CODE_ALTSCREEN_DISABLE);
}

void ansi_format(unsigned char format)
{
    printf("%s%dm", CSI, format);
}

void ansi_foreground(unsigned char color)
{
    if (color >= COLOR_EXTENDED)
    {
        printf("%s%d;%d;%dm", CSI, FORMAT_FOREGROUND + COLOR_EXTENDED, COLOR_EXTENDED_256, color);
    }
    else
    {
        ansi_format(FORMAT_FOREGROUND + color);
    }
}

void ansi_background(unsigned char color)
{
    if (color >= COLOR_EXTENDED)
    {
        printf("%s%d;%d;%dm", CSI, FORMAT_BACKGROUND + COLOR_EXTENDED, COLOR_EXTENDED_256, color);
    }
    else
    {
        ansi_format(FORMAT_BACKGROUND + color);
    }
}

// todo:
static int parse_modifiers(int input)
{

    return -1;
}

////////////////////////////// Sequence ///////////////////////////////////
typedef struct sequence
{
    char code;
    int params[3];
} sequence;

struct sequence *sequence_parse()
{
    // todo:
    return NULL;
}

void sequence_free(struct sequence *s)
{
    if (s != NULL)
    {
        free(s);
    }
}

/////////////////////////////////// Term //////////////////////////////////
void term_setup()
{
    struct termios t;
    tcgetattr(fileno(stdin), &t);
    term_original = t;
    t.c_iflag |= IGNBRK;
    t.c_iflag &= ~(INLCR | IXON | IXOFF);
    t.c_lflag &= ~(ICANON | ECHO | ECHOK | ECHOE | ECHONL | ISIG | IEXTEN);
    t.c_cc[VMIN] = 1;
    t.c_cc[VTIME] = 0;
    tcsetattr(fileno(stdin), TCSANOW, &t);

    // todo: discard term_load_size()
    ioctl(fileno(stdout), TIOCGWINSZ, &term_size);
    // todo
}

void term_close()
{
    tcsetattr(fileno(stdin), TCSANOW, &term_original);
    ansi_close();
}

void term_clear_screen()
{
    ansi_escape(CODE_ED);
}

void term_clear_line()
{
    ansi_escape(CODE_EL);
}

void term_flush()
{
    fflush(stdout);
}

void term_cursor_toggle(int show)
{
    if (show)
    {
        ansi_escape(CODE_CURSOR_SHOW);
    }
    else
    {
        ansi_escape(CODE_CURSOR_HIDE);
    }
}

void term_cursor_move(int x, int y)
{
    if (x < 0 || y < 0)
    {
        return;
    }
    printf("%s%d;%d%c", CSI, y + 1, x + 1, CODE_CUP);
}

int term_width()
{
    return (int)term_size.ws_col;
}

int term_height()
{
    return (int)term_size.ws_row;
}

void term_set_title(char *title)
{
    printf("%s%d;%s%c", OSC, OSC_SET_TITLE, title, OSC_END);
}

//////////////////////// Editor Current Status /////////////////////

typedef struct status
{
    char *filename;
    int cur_line;
    int cur_column;
    int selection_len;
} status;

#define DEFAULT_UNTITLED_FILENAME "untitled file"

void status_print(struct status *s)
{
    ansi_format(FORMAT_DIM);

    char *filename = s->filename;
    if (filename == NULL)
    {
        filename = DEFAULT_UNTITLED_FILENAME;
    }
    printf("%s", filename);

    printf("%d:%d", s->cur_line + 1, s->cur_column + 1);
    if (s->selection_len > 0)
    {
        printf(" (%d)", s->selection_len);
    }

    ansi_format(FORMAT_RESET);
}

////////////////////// Event ///////////////////////////////////////
#define KEY_ARROW_UP 1
#define KEY_ARROW_DOWN 2
#define KEY_ARROW_RIGHT 3
#define KEY_ARROW_LEFT 4
#define KEY_END 5
#define KEY_HOME 6
#define KEY_DELETE 7
#define KEY_BACKSPACE 8
#define KEY_ESC 9
#define KEY_PAGE_UP 10
#define KEY_PAGE_DOWN 11
#define KEY_INSERT 12

#define MODIFIER_SHIFT 1 << 1
#define MODIFIER_ALT 1 << 2
#define MODIFIER_CTRL 1 << 3

typedef struct event
{
    uint32_t ch;
    int key;
    int modifiers;
} event;

struct event *event_new()
{
    struct event *ev = (struct event *)malloc(sizeof(struct event));
    ev->ch = 0;
    ev->key = 0;
    ev->modifiers = 0;
    return ev;
}

void event_free(struct event *ev)
{
    if (ev != NULL)
    {
        free(ev);
    }
}

int event_equal(struct event *ev, struct event *rhs)
{
    return (ev->ch == rhs->ch && ev->key == rhs->key && ev->modifiers == rhs->modifiers);
}

struct event *event_read(FILE *file)
{
    struct event *e = NULL;
    uint32_t c = 0;
    uint32_t prev = 0;
    while (true)
    {
        int n = utf8_read_from(&c, file);
        if (n == EOF)
        {
            return NULL;
        }

        if (prev == (uint32_t)CSI[0] && c == (uint32_t)CSI[1])
        {
            struct sequence *s = sequence_parse();
            if (s == NULL)
            {
                return NULL;
            }

            int modifiers = parse_modifiers(s->params[1]);
            int key = 0;

            switch (s->code)
            {
                // todo
            }
            free(s);
            // todo
            return e;
        }
    }
}

////////////////////////// Line ///////////////////////////////////////////////////
typedef struct line
{
    struct lien *prev;
    struct line *next;

    uint32_t *chars;
    int len;
    int cap;
} line;

struct line *line_new()
{
    struct line *l = (struct line *)malloc(sizeof(struct line));

    l->prev = NULL;
    l->next = NULL;
    l->chars = NULL;
    l->len = 0;
    l->cap = 0;
    return l;
}

void line_free(struct line *l)
{
    if (l != NULL)
    {
        free(l->chars);
        free(l);
    }
}

//////////////////////// Selection //////////////////////////////////////

typedef struct selection
{
    struct line *line;
    int ch;
    int len;
    int dir;
} selection;

// Values for selection.dir.
#define SELECTION_DIR_RIGHT 0
#define SELECTION_DIR_LEFT 1

struct selection *selection_new()
{
    struct selection *s = (struct selection *)malloc(sizeof(struct selection));
    s->line = NULL;
    s->ch = 0;
    s->len = 0;
    s->dir = SELECTION_DIR_RIGHT;
    return s;
}

void selection_free(struct selection *s)
{
    free(s);
}

void selection_set_ch(struct selection *s, int at)
{
    struct line *l = NULL;
    int ch = 0;
    line_walk_char(s->line, at, &l, &ch);
    s->line = l;
    s->ch = ch;
}

void selection_move_ch(struct selection *s, int i)
{
    int at = s->ch;
    if (at > s->line->len)
    {
        at = s->line->len;
    }
    at += i;
    selection_set_ch(s, at);
}

void selection_shrink(struct selection *s, int dir)
{
    if (dir > 0)
    {
        selection_move_ch(s, s->len);
    }
    s->len = 0;
}

void selection_jump(struct selection *s, int dir)
{
    // todo:
}

void selection_extend_jump(struct selection *s, int dir)
{
    // todo:
}

//////////////////////////////////// Buffer ///////////////////////////////////////////
typedef struct buffer
{
    struct line *first;
    struct line *last;
    struct selection *sel;
} buffer;

// when typing key: tab
static void
tab_write_to(FILE *file)
{
#define TAB_MODE_TAB 0
#define TAB_MODE_SPACES 1
#define TAB_MODE_HOTDOG 2 // See https://twitter.com/Kuwaddo/status/784790536318312448

#define TAB_MODE TAB_MODE_HOTDOG

    switch (TAB_MODE)
    {
    case TAB_MODE_TAB:
        fprintf(file, "\t");
        break;
    case TAB_MODE_SPACES:
        fprintf(file, "    ");
        break;
    case TAB_MODE_HOTDOG:
        utf8_write_to(0x1F32D, file);
        break;
    default:
        break;
    }
}

void buffer_print(struct buffer *buf, struct status *s)
{
    int i = 0;
    int cur_line = 0;
    int cur_char = 0;
    int select_char = -1;
    int select_len = -1;
    for (struct line *l = buf->first; l != NULL; l = l->next)
    {
        if (buf->sel->line == l)
        {
            cur_line = i;
            select_char = buf->sel->ch;
            if (select_char > l->len)
            {
                select_char = l->len;
            }

            select_len = buf->sel->len;
            if (select_len == 0)
            {
                select_len = 1;
            }
        }

        uint32_t c;
        for (int j = 0; j <= l->len; j++)
        {
            if (j == l->len)
            {
                c = '\n';
            }
            else
            {
                c = l->chars[j];
            }
            if (select_len >= 0 && j == select_char)
            {
                ansi_format(FORMAT_REVERSE);
                cur_char = j;
                select_char = -1;
                if (j == l->len)
                {
                    printf(" ");
                }
            }

            if (c == '\t')
            {
                tab_write_to(stdout);
            }
            else
            {
                utf8_write_to(c, stdout);
            }

            if (select_char == -1 && select_len > 0)
            {
                select_len--;
                if (select_len <= 0)
                {
                    ansi_format(FORMAT_RESET);
                }
            }
        }
        i++;
    }

    // setup status bar ui
    if (s != NULL)
    {
        s->cur_line = cur_line;
        s->cur_column = cur_char;
        s->selection_len = buf->sel->len;
    }
}

struct buffer *buffer_new()
{
    struct buffer *b = malloc(sizeof(struct buffer));
    b->first = NULL;
    b->sel = NULL;
    // todo: buffer_reset
    return b;
}

void buffer_free(struct buffer *buf)
{
    struct line *l = buf->first;
    while (l != NULL)
    {
        struct line *next = l->next;
        line_free(l);
        l = next;
    }
    selection_free(buf->sel);
    free(buf);
}

////////////////////// Editor ///////////////////////////////////////
typedef struct editor
{
    struct buffer *buf;
    char *filename;
    int saved;
    char *status;
    int exit_code;
} editor;

struct editor *editor_new()
{
    struct editor *e = (struct editor *)malloc(sizeof(struct editor));
    e->buf = buffer_new();
    e->filename = NULL;
    e->saved = 1;
    e->status = NULL;
    e->exit_code = -1;
    return e;
}

void editor_free(struct editor *e)
{
    buffer_free(e->buf);
}

int editor_open(struct editor *e, char *filename);

int editor_save(struct editor *e)
{
    // todo:
    return -1;
}

char *editor_prompt(struct editor *e, char *prompt, int (*autocomplete)(char *val, char **results, int cap));

char *editor_prompt_filename(struct editor *e, char *prompt);

int editor_prompt_bool(struct editor *e, char *prompt);

void editor_set_status(struct editor *e, char *status)
{
}

void editor_quit(struct editor *e) {}

void editor_print(struct editor *e) {}

int editor_main(int argc, char **argv)
{
    // todo:
    return -1;
}

////////////////////// Command ////////////////////////////////////
typedef struct command
{
    char *title;
    struct event *ev;
    void (*exec)(struct editor *e, struct event *ev);
} command;

static struct command commands[13];

struct command *
command_match(struct command cmds[], int len, struct event *ev)
{
    for (int i = 0; i < len; i++)
    {
        if (event_equal(ev, cmds[i].ev))
        {
            return &cmds[i];
        }
    }
    return NULL;
}

// todo: funcs for command
void command_move(struct editor *e, struct event *evt);
void command_move_home(struct editor *e, struct event *evt);
void command_move_end(struct editor *e, struct event *evt);
void command_delete(struct editor *e, struct event *evt);
void command_select_all(struct editor *e, struct event *evt);
void command_cut_copy(struct editor *e, struct event *evt);
void command_go_to_line(struct editor *e, struct event *evt);
void command_select_line(struct editor *e, struct event *evt);
void command_quit(struct editor *e, struct event *evt);
void command_open(struct editor *e, struct event *evt);
void command_save(struct editor *e, struct event *evt);
void command_paste(struct editor *e, struct event *evt);
void command_palette(struct editor *e, struct event *evt);

////////////////////// Clipboard ////////////////////////////////
void clipboard_init();

FILE *clipboard_open(char *mode);

int clipboard_close(FILE *file);

/////////////////////////////////// Main ////////////////////////////////////
int main(int argc, char **argv)
{
    // turn off buffering for stdin
    setvbuf(stdin, NULL, _IONBF, 0);

    term_setup();

    term_cursor_toggle(0);

    int ec = editor_main(argc, argv);
    term_clear_screen();
    term_cursor_toggle(1);
    term_close();
    return ec;
}

static struct command commands[13] = {
    // todo: add command here
    0,
};

// implementations
void command_move(struct editor *e, struct event *evt);
void command_move_home(struct editor *e, struct event *evt);
void command_move_end(struct editor *e, struct event *evt);
void command_delete(struct editor *e, struct event *evt);
void command_select_all(struct editor *e, struct event *evt);
void command_cut_copy(struct editor *e, struct event *evt);
void command_go_to_line(struct editor *e, struct event *evt);
void command_select_line(struct editor *e, struct event *evt);
void command_quit(struct editor *e, struct event *evt);
void command_open(struct editor *e, struct event *evt);
void command_save(struct editor *e, struct event *evt);
void command_paste(struct editor *e, struct event *evt);
void command_palette(struct editor *e, struct event *evt);
